package com.ruoyi.aitcommon.config;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.ruoyi.aitapp.utils.APPConfigUtils;
import com.ruoyi.aitcommon.utils.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.utils.StringUtils;

import cn.hutool.core.util.StrUtil;

/**
 * 自动加载文件配置
 * 管理context
 * @author Administrator
 *
 */
@Component
public class ConfigManager {
//	记录配置上下文
	private Map<String,Object> configContext=new LinkedHashMap<>();
//	记录配置文件信息
	private Map<String,Object> configInfo=new LinkedHashMap<>();

	@Value("${ait.configpath}")
	String configPath;
	@Value("${ait.path}")
	String aitPath;
	//${ait.configdir:}配置最后的冒号代表默认值空字符串
	@Value("${ait.configdir:}")
	String configDir;
	
	@PostConstruct
	void init() throws Exception {
		this.loadConfig();
	}
	/**
	 * 初始加载配置文件
	 * 程序启动时调用
	 */
	public void loadConfig() throws Exception {
		try {
			if(StringUtils.isBlank(configPath)) {
				LogUtils.warn("配置文件application-ait.yml中ait.configpath为空");
				return;
			}
				
			String[] splits = configPath.split(",");
			for(String dir:splits) {
				loadConfigByDir(dir);
			}
//			return;
		}catch(Exception ex) {
			ExceptionUtils.throwException(ex, "ConfigManager配置文件加载失败!");
		}
	}
	/**
	 * 依据文件路径,重新加载源文件
	 * @param path 文件路径
	 */
	public void reloadConfigByPath(String path) throws Exception {
//		查找到配置信息
		LogUtils.debug("ConfigManager重新读取:"+path);
		ConfigInfoVO vo = this.getConfigInfoByPath(path);
		if(vo==null) {
			String msg="上下文中不存在[%s]对应配置信息!";
			msg=String.format(msg, path);
			throw new Exception(msg);
		}
		
		Object value = this.getConfigValue(vo.getPath());
		this.setContextByParm(vo.getParmname(), value);
		
//		更新InfoVO
		if(value instanceof JSONObject) {
			JSONObject json=(JSONObject)value;
			String zwName=json.getString("name");
			String desc=json.getString("desc");
			vo.setZwName(zwName);
			vo.setDesc(desc);
		}
	}
	/**
	 * 依据变量名称,重新加载源文件
	 * @param parm 参数Key
	 */
	public void reloadConfigByParm(String parm) throws Exception {
		LogUtils.debug("ConfigManager重新读取:"+parm);
		ConfigInfoVO vo = this.getConfigInfoByParm(parm);
		if(vo==null) {
			String msg="上下文中不存在[%s]对应配置信息!";
			msg=String.format(msg, parm);
			throw new Exception(msg);
		}
		Object value = this.getConfigValue(vo.getPath());
		this.setContextByParm(vo.getParmname(), value);
		
//		更新InfoVO
		if(value instanceof JSONObject) {
			JSONObject json=(JSONObject)value;
			String zwName=json.getString("name");
			String desc=json.getString("desc");
			vo.setZwName(zwName);
			vo.setDesc(desc);
		}
	}

	/**
	 * 依据文件路径获取源文件配置对象
	 * 支持文件 JSON,SQL
	 * @param filePath 文件绝对路径
	 */
	public Object getConfigValue(String filePath) throws Exception {
		try {
			String text = FileUtils.readFileLines(filePath);
			Object value=null;
			if(filePath.endsWith(".json")) {
//				JSON处理
				value = JSONUtils.parseObject(text);
			}else if(filePath.endsWith(".sql")) {
//				SQL处理
				value=SQLUtils.removeComment(text);
			}
			return value;
		}catch(Exception ex) {
			ExceptionUtils.throwException(ex, "文件[{}]解析失败!", filePath);
		}
		return null;
	}
	
	/**
	 * 判断flow中的参数是否配置文件中存在的参数
	 * 因为flow中的部分参数是flow文件内部变量
	 * @param parm 举例config.aitconfig.datasource.aitdb
	 * @return
	 */
	public boolean isConfigParm(String parm) {
		for(String dir:configInfo.keySet()) {
			String prefix=dir+".";
			if(StrUtil.startWith(parm, prefix))
				return true;
		}
		return false;
	}
	
	/**
	 * 通过参数编码获取对应的参数配置信息
	 * @param parm 参数编码
	 * @return 参数配置信息VO
	 */
	public ConfigInfoVO getConfigInfoByParm(String parm) {
		List<ConfigInfoVO> list = this.getConfigInfoList(null);
		for(ConfigInfoVO vo : list) {
			if(parm.equals(vo.getParmname())||parm.startsWith(vo.getParmname()+".")) {
				return vo;
			}
		}
		return null;
	}
	
//	通过文件路径获取对应的配置文件信息
	public ConfigInfoVO getConfigInfoByPath(String path) {
		List<ConfigInfoVO> list = this.getConfigInfoList(null);
		for(ConfigInfoVO vo : list) {
			if(path.equals(vo.getPath())) {
				return vo;
			}
		}
		return null;
	}
	
	/**
	 * 读取AIT配置文件
	 * @param path 配置文件相对路径
	 * @return 文件内容String
	 */
	public String loadConfigFileStr(String path) throws Exception {
		String path1=this.getAitPath(path);
		String txt=FileUtils.getResourceFile(path1);
		return txt;
	}
	
	/**
	 * 将目录下的文件读取到context中
	 * 现只支持一级目录
	 * @param dir 目录
	 */
	private void loadConfigByDir(String dir) throws Exception {
		Map<String,Object> contextMap=new LinkedHashMap<>();
		Map<String,ConfigInfoVO> infoMap=new LinkedHashMap<>();
//		获取目录下的所有JSON,SQL等配置文件
		String dir1 = getAitPath(dir);
//		不再读取JAVA包中的资源文件,只读取外部文件
//		List<String> pathlist = FileUtils.getResourceList(dir1,"*");
		List<String> pathlist = FileUtils.getFileList(dir1);
		if(pathlist==null||pathlist.size()==0){
//			目录下文件为空
			return;
		}
//		针对每个文件处理
		for(String path:pathlist) {
//			过滤文件
			if(!path.endsWith(".json")&&!path.endsWith(".sql")) {
//				现阶段只处理json,sql两种文件格式
				continue;
			}
			
//			读取文件
//			LogUtils.info("ConfigManager读取:"+path);
			Object value= this.getConfigValue(path);//配置内容
			String type = FileUtils.getExtName(path);//配置类型,文件类型
			String fileName=FileUtils.getMainName(path);//文件名
			String parmName=getParamName(dir,path);//参数名
			String zwName="";//中文名
			String desc="";//描述
			if(value instanceof JSONObject) {
				JSONObject json=(JSONObject)value;
				zwName=json.getString("name");
				desc=json.getString("desc");
			}
			
//			构造configInfo
			ConfigInfoVO infoVO=new ConfigInfoVO();
			infoVO.setType(type);
			infoVO.setPath(path);
			infoVO.setDir(dir);
			infoVO.setFilename(fileName);
			infoVO.setFlowCode(parmName);
			infoVO.setParmname(parmName);
			infoVO.setZwName(zwName);
			infoVO.setDesc(desc);
			infoMap.put(fileName, infoVO);
//			构造context map
			MapUtils.putPathObject(parmName,contextMap,value);
		}
//		上下文信息构造
		this.configContext.putAll(contextMap);
//		上下文文件属性
		this.configInfo.put(dir, infoMap);
	}

	private String getAitPath(String path) {
		String ret=path;
//		基于苹果系统目录调整,两个window/mac系统均支持 /
		if(StrUtil.isNotBlank(this.configDir)){
			ret=this.configDir+"/"+ret;
		}
		if(StrUtil.isNotBlank(this.aitPath)) {
			ret =this.aitPath+"/"+ ret;
		}
		return ret;
	}

	/**
	 * 从文件路径中获取参数名
	 * @param dir yml中配置的加载目录
	 * @param path 文件绝对路径
	 * @return 变量名
	 */
	private String getParamName(String dir, String path) throws Exception {
		String param=dir+".";
		String fileName=FileUtils.getMainName(path);//文件名
		final String fullPath = FileUtils.getFullPath(path);//文件路径
// 找到 dir 的位置
		String dir1="\\"+dir+"\\";
		int configIndex = fullPath.indexOf(dir1);
		if (configIndex == -1) {
			String msg="[%s]路径中未找到[%s]";
			msg=String.format(msg,fullPath,dir1);
			throw new Exception(msg);
		}
		// 截取 dir 后的部分
		String subPath = fullPath.substring(configIndex + dir1.length());

		// 分割路径，获取子目录
		String[] subDirectories = subPath.split("\\\\");

		// 过滤掉空字符串并输出
		for (String subdir : subDirectories) {
			if(ObjUtils.isNotBlank(subdir))
				param=param+subdir+".";
		}
		param=param+fileName;

		return param;
	}

	public Map<String,Object> getContext(){
		return this.configContext;
	}

	/**
	 * 依据参数编码获取参数内容
	 * @param parm 参数Key,如 config.aitconfig
	 * @return 参数Object
	 */
	public Object getContextByParm(String parm) throws Exception {
		return getContextByParm(parm,false);
	}
	/**
	 * 依据参数编码获取参数内容
	 * @param parm 参数Key,如 config.aitconfig;
	 * @param transParams 是否翻译参数内容中的变量${}
	 * @return 参数Object
	 */
	public Object getContextByParm(String parm,boolean transParams) throws Exception {
		Object ret = MapUtils.getPathObject(parm, this.configContext);
		if(ret==null) {
			ExceptionUtils.throwException(null, "参数[{}]不存在!", parm);
		}
		if(StrUtil.isBlankIfStr(ret)) {
//			ret=""代表有该参数,但是值为""
			LogUtils.warn("Context参数[{}]获取值为空!", parm);
		}

		if(transParams){
//			变量翻译
			ret=JSONParamUtils.transParamsJSON(ret,getContext());
		}
		return ret;
	}

	private void setContextByParm(String parm,Object value) throws Exception {
		MapUtils.putPathObject(parm, this.configContext, value);
	}

	private void setContextArrayByParm(String parm,String fieldName,JSONObject value,Boolean isDelete) throws Exception {
		final Object pathObject = MapUtils.getPathObject(parm, this.configContext);
		if(!(pathObject instanceof JSONArray)){
			ExceptionUtils.throwException(null, "参数[{}]不是JSONArray!", parm);
		}
		JSONArray pathArray = (JSONArray)pathObject;
		final String keyValue = value.getString(fieldName);
		ExceptionUtils.checkBlankOrUndefined(keyValue, "参数[{}]字段[{}]为空!", parm, fieldName);
		final int index = JSONUtils.FindJSONArrayIndex(pathArray, fieldName, keyValue);
		if(isDelete&&index>=0){
			//删除
			pathArray.remove(index);
		}else if(index>=0){
			//匹配到后直接更新
			JSONObject matchJson = pathArray.getJSONObject(index);
			matchJson.putAll(value);
		}else{
			//未匹配到则添加
			pathArray.add(value);
		}
	}

	/**
	 * 依据参数编更新参数内容,并保存至文件
	 * @param parm 参数Key,如 config.aitconfig;
	 * @param parmJson 参数值,可以是JSONObject或JSONArray
	 */
	public void saveContextByParm(String parm, JSON parmJson) throws Exception {
		//更新context
		this.setContextByParm(parm,parmJson);
		//更新后验证
		ConfigUtils.verifyConfig(parm, this.configContext);
		//保存至文件
		this.saveContext2File(parm);
	}

	public void saveContextArrayByParm(String parm,String fieldName, JSON parmJson,Boolean isDelete) throws Exception {
		//更新context
		this.setContextArrayByParm(parm, fieldName,(JSONObject) parmJson,isDelete);
		//更新后验证
		ConfigUtils.verifyConfig(parm, this.configContext);
		//保存至文件
		this.saveContext2File(parm);
	}

	/**
	 * 保存参数内容至文件
	 * @param parm 参数Key,如 config.aitconfig;
	 */
	private void saveContext2File(String parm) throws Exception {
		MapUtils.pathForeach(this.configInfo, (key, value) -> {
			ConfigInfoVO vo = (ConfigInfoVO)value;
			String parm1=vo.getParmname()+ ".";//文件对应的参数名
			if(parm.startsWith(parm1)){
				final String filePath = vo.getPath();
				final String fileParm = vo.getParmname();
				final Object ParmObj = this.getContextByParm(fileParm);
				final JSON ParmJson = (JSON)ParmObj;
				FileUtils.writeFile(filePath, ParmJson.toString(SerializerFeature.PrettyFormat));
				LogUtils.info("保存参数文件:"+filePath);
			}
		});
	}

	/**
	 * 依据参数编码判断参数是否存在
	 * @param parm 参数Key,如 config.aitconfig;
	 * @return 参数Object
	 */
	public boolean hasContextByParm(String parm) throws Exception {
		Object ret = MapUtils.getPathObject(parm, this.configContext);
		if(ObjUtils.isBlank(ret)) {
			return false;
		}else{
			return true;
		}
	}

	/**
	 *依据参数编码获取参数类型
	 * @param path 参数编码
	 * @return 参数类型
	 */
	public String getContextTypeByPath(String path) throws Exception {
		Object ret=MapUtils.getPathObject(path, this.configInfo);
		if(ret==null) {
//			如果ret为空,应该是json内部的变量
			return "json";
		}else {
			ConfigInfoVO vo=(ConfigInfoVO)ret;
			return vo.getType();
		}
	}
	
	public List<ConfigInfoVO> getConfigInfoList(ConfigInfoVO queryVO){
		List<ConfigInfoVO> retlist=new ArrayList<ConfigInfoVO>();
		
		for(String key : configInfo.keySet()) {
			Object ret = configInfo.get(key);
			if(ret instanceof Map) {
				Map<String, Object> map1=(Map<String,Object>)ret;
				for(String key1:map1.keySet()) {
					ConfigInfoVO vo = (ConfigInfoVO)map1.get(key1);
					if(queryVO==null) {
						retlist.add(vo);
					}else {
//						存在过滤条件
						if(StrUtil.isNotBlank(queryVO.getDir())&&!vo.getDir().equals(queryVO.getDir()))
							continue;
						if(StrUtil.isNotBlank(queryVO.getParmname())&&!vo.getParmname().contains(queryVO.getParmname()))
							continue;
						if(StrUtil.isNotBlank(queryVO.getZwName())) {
							if(StrUtil.isBlankIfStr(vo.getZwName()))
								continue;
							if(!vo.getZwName().contains(queryVO.getZwName()))
								continue;
						}
//						处理返回
						retlist.add(vo);
					}
				}
			}
		}
		
		return retlist;
	}

	/**
	 * 返回加载的一级目录
	 * @return 一级目录列表
	 */
	public List<String> getDirs(){
		List<String> dirs=new ArrayList<>();
		for(String key : configInfo.keySet()) {
			dirs.add(key);
		}
		return dirs;
	}
	
}
